package com.licode.codekit.db;

import com.intellij.openapi.util.text.StringUtil;
import com.licode.codekit.utils.StrUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 根据表结构创建实体类
 *
 * @author licode
 */
public class EntityBuilder {
    public static final String intend = "    ";
    private static final Map<String, String> typeMapping = new HashMap<>();

    private String packageName = "";

    static {
        typeMapping.put("tinyint", "Integer");
        typeMapping.put("smallint", "Integer");
        typeMapping.put("mediumint", "Integer");
        typeMapping.put("int", "Integer");
        typeMapping.put("bigint", "Long");
        typeMapping.put("bool", "Boolean");
        typeMapping.put("bit", "Integer");

        typeMapping.put("float", "BigDecimal");
        typeMapping.put("double", "BigDecimal");
        typeMapping.put("decimal", "BigDecimal");
        typeMapping.put("real", "BigDecimal");

        typeMapping.put("datetime", "LocalDateTime");
        typeMapping.put("timestamp", "LocalDateTime");
        typeMapping.put("date", "LocalDate");
        typeMapping.put("time", "LocalTime");

        typeMapping.put("blob", "InputStream");
        typeMapping.put("binary", "InputStream");
        typeMapping.put("bfile", "InputStream");
        typeMapping.put("clob", "InputStream");
        typeMapping.put("raw", "InputStream");
        typeMapping.put("image", "InputStream");
    }

    public void generate(Table table, GenerateCtx ctx) {
        String clsFlag = ctx.getSuffix();
        String className;

        ctx.setEntityName(javaClassName(table.getName(), ctx.getTablePrefix()));
        ctx.setEntityField(StrUtil.firstLower(ctx.getEntityName()));
        className = ctx.getEntityName() + clsFlag;

        packageName = getPackageName(ctx.getDir());
        try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(
                new FileOutputStream(new File(ctx.getDir(), className + ".java")),
                StandardCharsets.UTF_8))) {
            generate(printWriter, className, table, ctx);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String getPackageName(String dir) {
        String pkgName = dir.replaceAll("\\\\", ".")
                .replaceAll("/", ".")
                .replaceAll("^.*src(\\.main\\.java\\.)?", "");
        pkgName = pkgName.replaceAll("\\.\\.", ".");
        pkgName = pkgName.replaceAll("\\.\\.", ".");

        if (pkgName.endsWith(".")) {
            return pkgName.substring(0, pkgName.length() - 1);
        }
        return pkgName;
    }

    private void generate(PrintWriter out, String className,
                          Table table, GenerateCtx ctx) {
        if (!ctx.getEntityMode()) {
            out.println("package " + packageName + ";");
            out.println("");
        }
        // out.println("import com.baomidou.mybatisplus.annotation.IdType;"
        // out.println("import com.baomidou.mybatisplus.annotation.TableId;"
        String tabComment = table.getComment();
        if (StringUtil.isEmpty(tabComment)) {
            tabComment = className;
        }

        boolean isInputId = false;
        Set types = new HashSet();
        for (Column col : table.getColumns()) {
            types.add(typeMapping.getOrDefault(col.getType(), "String"));
            if (col.isPrimary() && col.type.contains("char")) {
                isInputId = true;
            }
        }

        out.println("import lombok.Data;");
        out.println("import lombok.ToString;");

        if (!ctx.isPO()) {
            if (ctx.getJdkVersion() >= 17) {
                out.println("import java.io.Serial;");
            }
            out.println("import java.io.Serializable;");
        }

        if (types.contains("InputStream")) {
            out.println("import java.io.InputStream;");
        }
        if (types.contains("BigDecimal")) {
            out.println("import java.math.BigDecimal;");
        }

        if (types.contains("LocalDateTime")) {
            out.println("import java.time.LocalDateTime;");
        } else if (types.contains("LocalTime")) {
            out.println("import java.time.LocalTime;");
        } else if (types.contains("LocalDate")) {
            out.println("import java.time.LocalDate;");
        }

        if (ctx.isPO()) {
            if (isInputId) {
                out.println("import com.baomidou.mybatisplus.annotation.IdType;");
                out.println("import com.baomidou.mybatisplus.annotation.TableId;");
            }
            out.println("import com.baomidou.mybatisplus.annotation.TableName;");
        }
        out.println("");

        tabComment = tabComment.trim();
        while (tabComment.startsWith("\n")) {
            tabComment = tabComment.substring(1);
        }
        while (tabComment.endsWith("\n")) {
            tabComment = tabComment.substring(0, tabComment.length() - 1);
        }

        tabComment = tabComment.replace("\n", "\n * ");

        out.println("/**\n" +
                " * " + tabComment + "\n" +
                " * \n" +
                " * @author " + ctx.getAuthor() + "\n" +
                " * @date " + StrUtil.nowTime() + " \n" +
                " */");
        out.println("@Data");
        out.println("@ToString");
        if (ctx.isPO()) {
            out.println("@TableName (\"" + table.getName() + "\")");
            out.println("public class " + className + " {");
        } else {
            out.println("public class " + className + " implements Serializable {");
            out.println("");
        }

        if (!ctx.isPO()) {
            // JDK版本大于等于17，需要加上注解
            if (ctx.getJdkVersion() >= 17) {
                out.println(intend + "@Serial");
            }
            out.println(genSerialID());
        }

        for (Column col : table.getColumns()) {
            String fieldName = changeStyle(col.getName(), true, false);
            String fieldType = typeMapping.getOrDefault(col.getType(), "String");
            out.println("");
            // 输出注释
            if (isNotEmpty(col.getComment())) {
                out.println(intend + "/**");
                out.println(intend + " * " + col.getComment());
                out.println(intend + " */");
            }

            if (ctx.isPO() && col.isPrimary() && col.type.contains("char")) {
                out.println(intend + "@TableId(type = IdType.INPUT)");
            }
            // 输出成员变量
            out.println(intend + "private " + fieldType + " " + fieldName + ";");
        }

        out.println("");
        out.println("}");
    }

    /**
     * 处理类名（这里是因为我的表都是以t_命名的，所以需要处理去掉生成类名时的开头的T，
     * 如果你不需要那么请查找用到了 javaClassName这个方法的地方修改为 javaName 即可）
     *
     * @param str 表名称
     * @return 新的类名
     */
    public static String javaClassName(String str, int skipNum) {
        String clsName = str;
        if (skipNum > 0) {
            clsName = str.substring(skipNum);
        }
        return changeStyle(clsName, true, true);
    }

    public static String javaType(String type) {
        return typeMapping.getOrDefault(type, "String");
    }

    public static String joinStrArray(String[] strs, String split) {
        String retStr = "";
        for (String item : strs) {
            retStr += item + split;
        }
        return retStr.substring(0, retStr.length() - split.length());
    }

    public static String javaName(String str) {
        return changeStyle(str, true, false);
    }

    private boolean isNotEmpty(String content) {
        return content != null && content.trim().length() > 0;
    }

    public static String changeStyle(String str,
                                     boolean toCamel,
                                     boolean capitalize) {
        if (null == str || str.length() <= 1) {
            return str;
        }

        // 转换为驼峰
        if (toCamel) {
            return StrUtil.convertCamel(str.split("_"), capitalize);
        } else {
            return joinStrArray(str.split("_"), "");
        }
    }

    static String genSerialID() {
        return intend + "private static final long serialVersionUID = "
                + Math.abs(new Random().nextLong()) + "L;";
    }
}
